package org.softmed.rest.server.xstream;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.util.List;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.softmed.persistence.PersistenceManager;
import org.softmed.reflection.utils.FieldInfo;
import org.softmed.reflection.utils.ReflectionUtil;
import org.softmed.rest.config.Application;
import org.softmed.rest.generation.ScafoldFacade;
import org.softmed.rest.generation.fields.GenerationConfig;
import org.softmed.rest.generation.fields.FieldNameList;
import org.softmed.rest.generation.scafold.AppConfig;
import org.softmed.rest.generation.xml.XMLMarshaller;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;

import com.thoughtworks.xstream.XStream;

public class DefaultToXMLHideNonNativeConverter implements XMLMarshaller {

	// XMLMarshaller nonNantiveObjectConverter = new
	// DefaultToXMLListItemConverter();

	ReflectionUtil util = new ReflectionUtil();

	URIXMLEditor uriXmlEditor = new URIXMLEditor();

	private XStream streamer;

	private boolean detailed;

	public void setDetailed() {
		// DefaultToXMLHideNonNativeConverter conv = new
		// DefaultToXMLHideNonNativeConverter();
		// conv.setDetailed();
		// nonNantiveObjectConverter = conv;
		// nonNantiveObjectConverter.
		detailed = true;
	}

	public void setNonDetailed() {
		// nonNantiveObjectConverter = new DefaultToXMLListItemConverter();
		detailed = false;
	}

	public void setup(Class type, PersistenceManager odbUtil, Application app,
			Object... options) throws Throwable {

		if (options != null && options.length > 0) {
			String detail = (String) options[0];
			if (detail.equals("detailed")) {
				setDetailed();
			}
		}

		streamer = XStreamManager.getXStream();
		streamer.registerConverter(new SimpleListConverter());
		streamer.registerConverter(new DateConverter());
		streamer.registerConverter(new BigIntegerConverter());
		streamer.registerConverter(new BigDecimalConverter());
		// streamer.registerConverter(new TimestampConverter());
		// BigDecimal bd = new BigDecimal("");
		// BigInteger bi = null;
		// bi.toString();
		// Timestamp tm;
		// Timestamp.valueOf("");

		// Class<?> type = obj.getClass();

		AppConfig genApp = ScafoldFacade.getApp(app);
		GenerationConfig genCfg = genApp.getGenerationConfig();

		if (genCfg.getAlias(type) != null)
			streamer.alias(genCfg.getAlias(type), type);
		else
			streamer.alias(util.getHifenizedName(type.getSimpleName()), type);

		FieldInfo pkinfo = odbUtil.getPrimaryKeyInfo(type);
		if (pkinfo != null)
			streamer.useAttributeFor(pkinfo.getDeclaringClass(), pkinfo
					.getName());

		Class parent = type;
		do {
			FieldNameList attributeFields = genCfg.getAttributeFields(parent);
			List<String> attributes = null;
			if (attributeFields != null)
				attributes = attributeFields.getFieldNames();

			FieldNameList omitedFields = genCfg.getOmitedFields(parent);
			List<String> omited = null;
			if (omitedFields != null)
				omited = omitedFields.getFieldNames();

			// hide list items and other fields
			Class fieldType = null;
			ReflectionUtil util = new ReflectionUtil();
			List<FieldInfo> fields;

			List<String> nonDisclosedFields = util
					.getNonDeclaredFieldNames(parent);
			for (String fieldName : nonDisclosedFields) {
				streamer.omitField(parent, fieldName);
			}

			fields = util.getDeclaredFieldInfos(parent);
			for (FieldInfo fieldInfo : fields) {

				String alias = genCfg
						.getFieldAlias(parent, fieldInfo.getName());
				if (alias == null)
					alias = util.getHifenizedName(fieldInfo.getName());

				if (attributes != null
						&& attributes.contains(fieldInfo.getName())){
					streamer.useAttributeFor(parent, fieldInfo.getName());
//					streamer.aliasAttribute(parent, fieldInfo.getName(), alias);
					streamer.aliasField(alias, parent, fieldInfo.getName());
				}
				else
					streamer.aliasField(alias, parent, fieldInfo.getName());

				if (omited != null && omited.contains(fieldInfo.getName())) {
					streamer.omitField(parent, fieldInfo.getName());
					continue;
				}

				if (List.class.isAssignableFrom(fieldInfo.getType())) {
					if (util.isSpecificList(util.getField(type, fieldInfo
							.getName())) == null)
						streamer.omitField(parent, fieldInfo.getName());
					continue;
				}

				if (util.isPrimitiveOrProxyOrString(fieldInfo.getType()))
					continue;

				// omit non native fields - they are added later
				streamer.omitField(parent, fieldInfo.getName());

			}

			parent = parent.getSuperclass();
		} while (parent != null && parent != Object.class);
	}

	@Override
	public String toXML(Object obj, PersistenceManager odbUtil,
			Application app, Object... options) throws Throwable {

		try {

			String xml = streamer.toXML(obj);

			List<FieldInfo> fields = util
					.getNonNativeFieldInfos(obj.getClass());
			Object child = null;

			// DefaultToXMLListItemConverter conv = new
			// DefaultToXMLListItemConverter();

			Document parentDoc = getDocument(xml);

			for (FieldInfo fieldInfo : fields) {
				child = fieldInfo.getGetter().invoke(obj, null);
				if (child == null)
					continue;

				if (List.class.isAssignableFrom(fieldInfo.getType()))
					continue;

				XMLMarshaller nonNantiveObjectConverter = null;
				if (detailed) {
					nonNantiveObjectConverter = XMLCache.getNormalConverter(
							child.getClass(), odbUtil, app, options);
				} else {
					nonNantiveObjectConverter = XMLCache.getListConverter(child
							.getClass(), odbUtil, app, options);
				}

				String childXML = nonNantiveObjectConverter.toXML(child,
						odbUtil, app);
				int index = childXML.indexOf(' ');
				// remove the class name for the hifenized field name of parent
				// object
				if (index > 0)
					childXML = "\n<"
							+ util.getHifenizedName(fieldInfo.getName())
							+ childXML.substring(index) + "\n";

				childXML = uriXmlEditor.setURI(odbUtil, child, childXML, app);

				Document doc = getDocument(childXML);
				Element childRoot = doc.getDocumentElement();

				childRoot.getParentNode().removeChild(childRoot);
				parentDoc.getDocumentElement().appendChild(
						parentDoc.adoptNode(childRoot.cloneNode(true)));

			}

			xml = toString(parentDoc);

			// xml = uriXmlEditor.setURIObjectTree(odbUtil, obj, xml, app,
			// genCfg);
			xml = uriXmlEditor.setURI(odbUtil, obj, xml, app);
			return xml;

		} catch (Throwable e) {
			e.printStackTrace();
		}

		return null;

	}

	private Document getDocument(String xml) throws SAXException, IOException,
			ParserConfigurationException {
		DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory
				.newInstance();

		ByteArrayInputStream bais = new ByteArrayInputStream((xml)
				.getBytes(XStreamManager.CHAR_SET));
		// isr.

		Document doc = documentBuilderFactory.newDocumentBuilder().parse(bais);
		return doc;
	}

	public String toString(Node node) throws Throwable {
		StringWriter sw = new StringWriter();
		Transformer serializer = TransformerFactory.newInstance()
				.newTransformer();
		serializer.transform(new DOMSource(node), new StreamResult(sw));
		String string = sw.toString();
		if (string.startsWith("<?xml"))
			string = string.substring(string.indexOf('>') + 1);
		return string;
	}

	public DefaultToXMLHideNonNativeConverter clone() {
		return new DefaultToXMLHideNonNativeConverter();
	}

}
